home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Modules / BackSpaceModules / Source / Multi / NiftyMatrix.m < prev    next >
Text File  |  1992-09-09  |  11KB  |  351 lines

  1. // NiftyMatrix.m
  2. // By Jayson Adams, NeXT Developer Support Team
  3. // You may freely copy, distribute and reuse the code in this example.
  4. // NeXT disclaims any warranty of any kind, expressed or implied, as to its
  5. // fitness for any particular use.
  6.  
  7.  
  8. #import <dpsclient/psops.h>
  9. #import <dpsclient/wraps.h>
  10. #import <appkit/timer.h>
  11. #import <appkit/Cell.h>
  12. #import <appkit/Window.h>
  13. #import <appkit/Application.h>
  14. #import <math.h>
  15.  
  16. #import "NiftyMatrix.h"
  17.  
  18.  
  19. @implementation NiftyMatrix
  20.  
  21.  
  22. /* #defines stolen from Draw */
  23.  
  24. #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);
  25.  
  26. #define stopTimer(timer) if (timer) { \
  27.     NXEndTimer(timer); \
  28.     timer = NULL; \
  29. }
  30.  
  31. #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
  32.  
  33.  
  34. /* instance methods */
  35.  
  36. - free
  37. {
  38.     [matrixCache free];
  39.     [cellCache free];
  40.     
  41.     return [super free];
  42. }
  43.  
  44. - mouseDown:(NXEvent *)theEvent
  45. {
  46.     NXPoint        mouseDownLocation, mouseUpLocation, mouseLocation;
  47.     int            eventMask, row, column, newRow;
  48.     NXRect        visibleRect, cellCacheBounds, cellFrame;
  49.     id            matrixCacheContentView, cellCacheContentView;
  50.     float        dy;
  51.     NXEvent        *event, peek;
  52.     NXTrackingTimer    *timer = NULL;
  53.     BOOL        scrolled = NO;
  54.     
  55.   /* if the Control key isn't down, show normal behavior */
  56.     if (!(theEvent->flags & NX_CONTROLMASK)) {
  57.     return [super mouseDown:theEvent];
  58.     }
  59.     
  60.   /* prepare the cell and matrix cache windows */
  61.     [self setupCacheWindows];
  62.     
  63.   /* we're now interested in mouse dragged events */
  64.     eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  65.  
  66.   /* find the cell that got clicked on and select it */
  67.     mouseDownLocation = theEvent->location;
  68.     [self convertPoint:&mouseDownLocation fromView:nil];
  69.     [self getRow:&row andCol:&column forPoint:&mouseDownLocation];
  70.     activeCell = [self cellAt:row :column];
  71. // [self selectCell:activeCell];
  72.     [self getCellFrame:&cellFrame at:row :column];
  73.     
  74.   /* do whatever's required for a single-click */
  75. //  [self sendAction];
  76.     
  77.   /* draw a "well" in place of the selected cell (see drawSelf::) */
  78.     [self display:&cellFrame :1];
  79.     
  80.   /* copy what's currently visible into the matrix cache */
  81.     matrixCacheContentView = [matrixCache contentView];
  82.     [matrixCacheContentView lockFocus];
  83.     [self getVisibleRect:&visibleRect];
  84.     [self convertRect:&visibleRect toView:nil];
  85.     PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
  86.             NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
  87.         [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY);
  88.     [matrixCacheContentView unlockFocus];
  89.  
  90.   /* image the cell into its cache */
  91.     cellCacheContentView = [cellCache contentView];
  92.     [cellCacheContentView lockFocus];
  93.     [cellCacheContentView getBounds:&cellCacheBounds];
  94. //    PSsetgray([activeCell state] ? NX_WHITE : NX_LTGRAY);
  95.     PSsetgray(NX_LTGRAY);
  96.     NXRectFill(&cellCacheBounds);
  97.     [activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView];
  98.     [cellCacheContentView unlockFocus];
  99.  
  100.   /* save the mouse's location relative to the cell's origin */
  101.     dy = mouseDownLocation.y - cellFrame.origin.y;
  102.     
  103.   /* from now on we'll be drawing into ourself */
  104.     [self lockFocus];
  105.     
  106.     event = theEvent;
  107.     while (event->type != NX_MOUSEUP) {
  108.       
  109.       /* erase the active cell using the image in the matrix cache */
  110.     [self getVisibleRect:&visibleRect];
  111.     PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
  112.             NX_Y(&cellFrame) + NX_Y(&visibleRect) -
  113.             NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
  114.             NX_HEIGHT(&cellFrame), [matrixCache gState],
  115.             NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
  116.             NX_COPY);
  117.     
  118.       /* move the active cell */
  119.     mouseLocation = event->location;
  120.     [self convertPoint:&mouseLocation fromView:nil];
  121.     cellFrame.origin.y = mouseLocation.y - dy;
  122.     
  123.       /* constrain the cell's location to our bounds */
  124.     if (NX_Y(&cellFrame) < NX_X(&bounds) ) {
  125.         cellFrame.origin.y = NX_X(&bounds);
  126.     } else if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds)) {
  127.         cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
  128.     }
  129.  
  130.       /*
  131.        * make sure the cell will be entirely visible in its new location (if
  132.        * we're in a scrollView, it may not be)
  133.        */
  134.     if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll) {    
  135.       /*
  136.        * the cell won't be entirely visible, so scroll, dood, scroll, but
  137.        * don't display on-screen yet
  138.        */
  139.         [window disableFlushWindow];
  140.         [self scrollRectToVisible:&cellFrame];
  141.         [window reenableFlushWindow];
  142.         
  143.       /* copy the new image to the matrix cache */
  144.         [matrixCacheContentView lockFocus];
  145.         [self getVisibleRect:&visibleRect];
  146.         [self convertRect:&visibleRect toView:nil];
  147.         PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
  148.             NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
  149.             [window gState], 0.0, NX_HEIGHT(&visibleRect),
  150.             NX_COPY);
  151.         [matrixCacheContentView unlockFocus];
  152.         
  153.       /*
  154.        * note that we scrolled and start generating timer events for
  155.        * autoscrolling
  156.        */
  157.         scrolled = YES;
  158.         startTimer(timer);
  159.     } else {
  160.       /* no scrolling, so stop any timer */
  161.         stopTimer(timer);
  162.     }
  163.       
  164.       /* composite the active cell's image on top of ourself */
  165.     PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
  166.             [cellCache gState], NX_X(&cellFrame),
  167.             NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
  168.     
  169.       /* now show what we've done */
  170.     [window flushWindow];
  171.     
  172.       /*
  173.        * if we autoscrolled, flush any lingering window server events to make
  174.        * the scrolling smooth
  175.        */
  176.     if (scrolled) {
  177.         NXPing();
  178.         scrolled = NO;
  179.     }
  180.     
  181.       /* save the current mouse location, just in case we need it again */
  182.     mouseLocation = event->location;
  183.     
  184.     if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
  185.       /*
  186.        * no mouseMoved or mouseUp event immediately avaiable, so take
  187.        * mouseMoved, mouseUp, or timer
  188.        */
  189.         event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
  190.     } else {
  191.       /* get the mouseMoved or mouseUp event in the queue */
  192.         event = [NXApp getNextEvent:MOVE_MASK];
  193.     }
  194.     
  195.       /* if a timer event, mouse location isn't valid, so we'll set it */
  196.     if (event->type == NX_TIMER) {
  197.         event->location = mouseLocation;
  198.     }
  199.     }
  200.     
  201.   /* mouseUp, so stop any timer and unlock focus */
  202.     stopTimer(timer);
  203.     [self unlockFocus];
  204.     
  205.   /* find the cell under the mouse's location */
  206.     mouseUpLocation = event->location;
  207.     [self convertPoint:&mouseUpLocation fromView:nil];
  208.     if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation]) {
  209.       /* mouse is out of bounds, so find the cell the active cell covers */
  210.     [self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)];
  211.     }
  212.     
  213.   /* we need to shuffle cells if the active cell's going to a new location */
  214.     if (newRow != row) {
  215.       /* no autodisplay while we move cells around */
  216.     [self setAutodisplay:NO];
  217.     if (newRow > row) {
  218.       /* adjust selected row if before new active cell location */
  219.         if (selectedRow <= newRow) {
  220.         selectedRow--;
  221.         }
  222.     
  223.       /*
  224.        * push all cells above the active cell's new location up one row so
  225.        * that we fill the vacant spot
  226.        */
  227.         while (row++ < newRow) {
  228.         cell = [self cellAt:row :0];
  229.         [self putCell:cell at:(row - 1) :0];
  230.         }
  231.       /* now place the active cell in its new home */
  232.         [self putCell:activeCell at:newRow :0];
  233.     } else if (newRow < row) {
  234.           /* adjust selected row if after new active cell location */
  235.         if (selectedRow >= newRow) {
  236.         selectedRow++;
  237.         }
  238.     
  239.       /*
  240.        * push all cells below the active cell's new location down one row
  241.        * so that we fill the vacant spot
  242.        */
  243.         while (row-- > newRow) {
  244.         cell = [self cellAt:row :0];
  245.         [self putCell:cell at:(row + 1) :0];
  246.         }
  247.       /* now place the active cell in its new home */
  248.         [self putCell:activeCell at:newRow :0];
  249.     }
  250.       
  251.       /* if the active cell is selected, note its new row */
  252.     if ([activeCell state]) {
  253.         selectedRow = newRow;
  254.     }
  255.       
  256.       /* make sure the active cell's visible if we're autoscrolling */
  257.     if (mFlags.autoscroll) {
  258.         [self scrollCellToVisible:newRow :0];
  259.     }
  260.       
  261.       /* no longer dragging the cell */
  262.     activeCell = 0;
  263.     
  264.       /* size to cells after all this shuffling and turn autodisplay back on */
  265.     [[self sizeToCells] setAutodisplay:YES];
  266.     } else {
  267.       /* no longer dragging the cell */
  268.     activeCell = 0;
  269.     }
  270.     
  271.   /* now redraw ourself */
  272.     [self display];
  273.     
  274.   /* set the event mask to normal */
  275.     [window setEventMask:eventMask];
  276.  
  277.     return self;
  278. }
  279.  
  280. - drawSelf:(NXRect *)rects :(int)count
  281. {
  282.     int        row, col;
  283.     NXRect    cellBorder;
  284.     int        sides[] = {NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
  285.                    NX_YMIN};
  286.     float    grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE, NX_WHITE, NX_BLACK,
  287.                NX_BLACK};
  288.                
  289.   /* do the regular drawing */
  290.     [super drawSelf:rects :count];
  291.     
  292.   /* draw a "well" if the user's dragging a cell */
  293.     if (activeCell) {
  294.       /* get the cell's frame */
  295.     [self getRow:&row andCol:&col ofCell:activeCell];
  296.     [self getCellFrame:&cellBorder at:row :col];
  297.       
  298.       /* draw the well */
  299.     if (NXIntersectsRect(&cellBorder, &(rects[0]))) {
  300.         NXDrawTiledRects(&cellBorder, (NXRect *)0, sides, grays, 6);
  301.         PSsetgray(0.17);
  302.         NXRectFill(&cellBorder);
  303.     }
  304.     }
  305.     
  306.     return self;
  307. }
  308.  
  309. - setupCacheWindows
  310. {
  311.     NXRect    visibleRect;
  312.  
  313.   /* create the matrix cache window */
  314.     [self getVisibleRect:&visibleRect];
  315.     matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)];
  316.     
  317.   /* create the cell cache window */
  318.     cellCache = [self sizeCacheWindow:cellCache to:&cellSize];
  319.  
  320.     return self;
  321. }
  322.  
  323. - sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
  324. {
  325.     NXRect    cacheFrame;
  326.     
  327.     if (!cacheWindow) {
  328.       /* create the cache window if it doesn't exist */
  329.     cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
  330.     cacheFrame.size = *windowSize;
  331.     cacheWindow = [[[Window alloc] initContent:&cacheFrame
  332.                        style:NX_PLAINSTYLE
  333.                        backing:NX_RETAINED
  334.                        buttonMask:0
  335.                        defer:NO] reenableDisplay];
  336.       /* flip the contentView since we are flipped */
  337.     [[cacheWindow contentView] setFlipped:YES];
  338.     } else {
  339.       /* make sure the cache window's the right size */
  340.     [cacheWindow getFrame:&cacheFrame];
  341.     if (cacheFrame.size.width != windowSize->width ||
  342.               cacheFrame.size.height != windowSize->height) {
  343.         [cacheWindow sizeWindow:windowSize->width
  344.                        :windowSize->height];
  345.     }
  346.     }
  347.     
  348.     return cacheWindow;
  349. }
  350.  
  351. @end